#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <cstdint>
#include <sys/types.h>
#include <sys/socket.h>
#include <poll.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <fcntl.h>

#include <iostream>
#include <vector>
#include <string>
#include <iterator>

#include "server.h"

#define CHAT_PORT 6789

Server server;

static int readAll(int sock, char* buf, size_t len) {
  struct pollfd pfd;

  pfd.fd = sock;
  pfd.events = POLLIN | POLLERR;

  while (len) {
    int res = recv(sock, buf, len, MSG_DONTWAIT);

    if (res <= 0) {
      return -1;
    }

    buf += res;
    len -= res;
  }

  return 0;
}


Client::Client(int sock) { 
  _sock = sock;
  worker(this);
}

Client::~Client() {
  close(_sock);
}

void Client::send(const void* data, uint32_t len) {
  write(_sock, &len, sizeof(uint32_t));
  write(_sock, data, len);
}

void* Client::worker(Client* self) {
  uint32_t msg_len;
  char* msg;
  struct pollfd pfd;

  std::cout << "INFO: New client was added" << std::endl;

  fcntl(self->_sock, F_SETFL, O_NONBLOCK);

  pfd.fd = self->_sock;
  pfd.events = POLLIN | POLLERR;

  for (;;) {
    poll(&pfd, 1, -1);
    if (pfd.revents & POLLERR) {
      server.remove(self);
      return NULL;
    }    

    if (readAll(self->_sock, (char*)&msg_len, sizeof(msg_len)) < 0) {
      server.remove(self);
      return NULL;
    }

    msg = new char[msg_len + 1];
    if (readAll(self->_sock, msg, msg_len) < 0) {
      delete [] msg;
      server.remove(self);
      return NULL;
    }
    
    msg[msg_len] = 0;
    server.handle(self, msg);
    delete [] msg;
  }
}


Server::Server() {
  svr_sock = socket(AF_INET, SOCK_STREAM, 0);
  if (svr_sock == -1) {
    perror("Failed to create a TCP socket");
    _initialized = false;
    return;
  }

  bcast_sock = socket(AF_INET, SOCK_DGRAM, 0);
  if (bcast_sock == -1) {
    perror("Failed to create a UDP socket");
    _initialized = false;
    return;
  }  

  int reuseaddr_val = 1;
  setsockopt(svr_sock, SOL_SOCKET, SO_REUSEADDR, &reuseaddr_val, sizeof(int));
  setsockopt(bcast_sock, SOL_SOCKET, SO_REUSEADDR, &reuseaddr_val, sizeof(int));

  sockaddr_in_t svr_host;
  svr_host.sin_family = AF_INET;
  svr_host.sin_port = htons(CHAT_PORT);
  svr_host.sin_addr.s_addr = INADDR_ANY;

  if (bind(svr_sock, (sockaddr_t*)&svr_host, sizeof(svr_host)) == -1) {
    printf("Failed to bind to the TCP socket\n");
    _initialized = false;
    return;
  }

  if (bind(bcast_sock, (sockaddr_t*)&svr_host, sizeof(svr_host)) == -1) {
    printf("Failed to bind to the TCP socket\n");
    _initialized = false;
    return;
  }  

  listen(svr_sock, 5);

  _initialized = true;
}

Server:: ~Server() {
  if (bcast_sock != -1) {
    close(bcast_sock);
  }

  if (svr_sock != -1) {
    close(svr_sock);
  }
}

bool Server::isOk() const {
  return _initialized;
}

void Server::remove(Client* client) {
  _clients.remove(client);
  _clientNames.erase(client->name());
  delete client;
}

int Server::mainLoop() {
  if (!isOk()) {
    return 1;
  }

  struct pollfd p[2];
    
  p[0].fd = svr_sock;
  p[0].events = POLLIN;
  p[1].fd = bcast_sock;
  p[1].events = POLLIN;

  for (;;) {
    poll(p, 2, -1);

    if (p[0].revents & POLLIN) {
      int sock = accept(svr_sock, NULL, NULL);
      if (sock > 0) {
	_clients.push_back(new Client(sock));
      }
    }

    if (p[1].revents & POLLIN) {
      sockaddr_t addr;
      socklen_t addrlen = sizeof(addr);
      char msg[1024];

      int len = recvfrom(bcast_sock, msg, sizeof(msg), 0, &addr, &addrlen);
      if (len > 0) {
        handleBroadcast(msg, &addr);
      }
    }
  }

  return 0;
}

void Server::handle(Client* client, const char* msg) {
  if (strncmp(msg, "HELLO", 5) == 0) {
    uint32_t cnLen = *(uint32_t*)msg;
    client->setName(std::string(msg + sizeof(uint32_t), cnLen));
    _clientNames.insert(client->name());

    client->send(msg, cnLen);	
  } else if (strncmp(msg, "GET", 3) == 0) {
      std::string res;	
      uint32_t id = 0;
      std::list<std::string>::iterator it = _messages.begin();

      char last_id[32];
      sprintf(last_id, "%u", (uint32_t)_messages.size());
      

      sscanf(msg + 3 + sizeof(uint32_t), "%u", &id);
      std::advance(it, id);

      for (; it != _messages.end(); ++it) {
	res += it->c_str();
      }

      client->send(res.c_str(), res.size());	
    } else if (strncmp(msg, "BYE", 3) == 0) {
    } else if (strncmp(msg, "SEND", 4) == 0) {
      _messages.push_back(std::string(msg + 4 + sizeof(uint32_t)));
    }  
}

void Server::handleBroadcast(const char* msg, const sockaddr_t* addr) {
  std::string hello = "Hello";
  sendto(bcast_sock, hello.c_str(), hello.size(), 0, addr, sizeof(sockaddr_in_t));
}

int main(int argc, char* argv[]) {
  return server.mainLoop();
}
